# -*- coding: utf-8 -*-
import re
import os
import email
import chardet
import html2text
import traceback
import threading
import hashlib
from bs4 import BeautifulSoup
from datetime import datetime
from sinotrans.utils import Logger
from email.header import decode_header
from sinotrans.core import FileProcessor
from api.email_ai_api import EmailAIService
from conf.prompt_field_values import is_prompt_example_value

# 抑制深度学习框架警告
os.environ['TRANSFORMERS_NO_ADVISORY_WARNINGS'] = 'true'
os.environ['TOKENIZERS_PARALLELISM'] = 'false'

class EmlAidata:
    @classmethod
    def from_config(cls, config_params):
        """根据配置参数创建EmlAidata实例
        
        Args:
            config_params (dict): 包含所有初始化参数的配置字典
            
        Returns:
            EmlAidata: 配置好的EmlAidata实例
        """
        # 创建实例但不调用__init__
        instance = cls.__new__(cls)
        
        # 手动初始化基础属性
        instance.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        instance.current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        instance.email_attachment_keywords = [
            "BOSCH 2025",
            "Sinotrans - PTMA - Ocean Import Daily Progress Report",
            "SEA SINOTRANS Tracking Report Bosch",
            "SEASINOTRANS RBIY",
            "SEASINOTRANS DCID"
        ]
        # 从配置中获取参数，如果没有则使用默认值
        instance.email_folder_version = config_params.get('email_folder_version', 'V12')
        # instance.max_retries = config_params.get('max_retries', 5)
        # instance.email_folder = config_params.get('email_folder', 'INBOX')
        instance.forward_patterns = config_params['forward_patterns']
         
        # 调用初始化方法
        instance._init_paths()
        instance._init_logger()
        
        # 初始化EmailAIService
        instance.email_ai_service = EmailAIService(timeout_dir=os.path.join(instance.log_path, "time_out"))
        
        return instance

    def _init_paths(self):
        """初始化所有路径配置"""
        self.target_path = os.path.join(self.current_dir, "target")
        self.config_path = os.path.join(self.current_dir, "conf")
        self.log_path = os.path.join(self.current_dir, "logs")
        self.email_attachment_path = os.path.join(self.current_dir, "email_attachment")
        
        self.template_file = os.path.join(self.current_dir, "template.xlsx")
        self.target_file = os.path.join(self.target_path, f"Email_Aidata_{self.email_folder_version}.xlsx")
        self.sheet_conf =  os.path.join(self.config_path, "sheet_config.txt")
        
        # 使用FileProcessor确保所有目录存在
        FileProcessor.ensure_directories_exist([
            self.target_path, 
            self.config_path,
            self.email_attachment_path
            # *dir_paths  # 展开dir_info中的所有目录路径
        ])

    def _init_logger(self):
        """初始化日志系统"""
        debug_path = os.path.join(self.current_dir, "logs")
        Logger(debug_path=debug_path)
    # 获取委托单号
    def _get_ID(self, text):
        """
        正则匹配获取'WT\d{14}'邮件主题中的委托单号
        :param text: String
        :return: String
        """
        # 定义正则表达式模式
        pattern = 'WT\d{14}'
        # 使用 re.findall() 查找所有匹配项
        matches = re.findall(pattern, text)
        if len(matches) == 0:
            return "主题中无委托单号"
        else:
            return matches[0]
    # 处理正文的转发字段
    def _find_substring_and_trim(self, text, substring):
        # 查找子字符串的位置
        start_pos = text.find(substring)

        if start_pos != -1:
            # 截取从子字符串开始到字符串末尾的部分
            trimmed_text = text[:start_pos]
            return trimmed_text
        else:
            # 如果没有找到子字符串，返回原始字符串
            return text
    # # 解码发件人和主题
    # def _decode_mime_header(self, header):
    #     """解码 MIME 编码的邮件头（如发件人、主题）"""
    #     decoded_parts = []
    #     for part, encoding in decode_header(header):
    #         if isinstance(part, bytes):
    #             try:
    #                 decoded = part.decode(encoding or 'utf-8', errors='replace')
    #             except:
    #                 decoded = part.decode('latin-1', errors='replace')
    #             decoded_parts.append(decoded)
    #         else:
    #             decoded_parts.append(part)
    #     return ''.join(decoded_parts)
    # def utf7_encode(s: str) -> bytes:
    #     # 将字符串编码为 Modified UTF-7（IMAP 专用）
    #     s_utf7 = s.encode('utf-7').replace(b'+', b'&').replace(b',', b'-')
    #     return s_utf7
    def _safe_decode(self, payload_bytes, preferred_encoding=None):
        """
        安全解码字节数据为字符串，优先使用指定编码
        
        Args:
            payload_bytes: 要解码的字节数据
            preferred_encoding: 优先使用的编码（通常来自decode_header）
        """
        if not payload_bytes:
            return ""
        
        # 如果提供了首选编码，优先尝试
        if preferred_encoding:
            try:
                return payload_bytes.decode(preferred_encoding)
            except (UnicodeDecodeError, LookupError):
                pass
        
        # 检测编码
        detection = chardet.detect(payload_bytes)
        encoding = detection['encoding'] if detection['encoding'] else 'utf-8'
        
        # 备选编码列表
        fallback_encodings = ['gb18030', 'gbk', 'utf-8', 'latin1']
        
        # 尝试解码
        for enc in [encoding] + fallback_encodings:
            try:
                return payload_bytes.decode(enc)
            except (UnicodeDecodeError, LookupError):
                continue
        
        # 最终安全解码
        try:
            return payload_bytes.decode('gb18030', errors='replace')
        except:
            return payload_bytes.decode('latin1', errors='replace')
    def _set_field_by_name(self, output_ws, col_name: str, chinese_headers, english_headers, row_idx: int, col_value):
        """
        根据列名设置列值
        :param output_ws: 输出工作表
        :param source_col_name: 源列名
        :param chinese_headers: 中文列名列表
        :param english_headers: 英文列名列表
        :param row_idx: 行索引
        :param email_subject: 邮件主题
        :return: None
        """
        if col_name in chinese_headers:
            source_col_idx = self._get_col_idx_by_name(col_name, chinese_headers, strict_flag=True)
            if source_col_idx >= 0:  # 列索引有效
                output_ws.cell(row=row_idx, column=source_col_idx+1, value=col_value)
    def _get_col_idx_by_name(self, header_str, chinese_headers, english_headers=None, strict_flag=False):
        try:
            col_index = chinese_headers.index(header_str)
            return col_index
        except ValueError:
            try:
                # 尝试匹配英文列名
                col_index = english_headers.index(header_str)
            except ValueError:
                if strict_flag:
                    return -1
                # 默认使用第一列
                col_index = 0
                Logger.debug(f"未找到列名{header_str}，将使用第一列作为委托单号列")
        
    def _update_or_append_to_sheet(self, output_ws, email_Aidata_lst):
        """
        更新或追加数据到工作表，处理重复委托单号情况
        :param output_ws: 目标工作表对象
        :param ordered_rows: 待处理的数据行列表（字典组成的列表）
        """
        # 获取表头信息
        english_headers = [cell.value for cell in output_ws[1]]  # 第一行: 英文列名
        chinese_headers = [cell.value for cell in output_ws[2]]  # 第二行: 中文列名

        # 确定"委托单号"、"数据更新时间"列位置（优先匹配中文列名）
        delegate_header = "委托单号"
        update_time_header  = "数据更新时间"
        
        delegate_col_index = self._get_col_idx_by_name(delegate_header, chinese_headers, english_headers)

        # 构建工作表现有委托单号的索引（行号映射）
        existing_delegate_ids = {}
        for row_idx, row in enumerate(output_ws.iter_rows(min_row=3, values_only=False), start=3):
            existing_wt = str(row[delegate_col_index].value) if row[delegate_col_index].value else ""
            if existing_wt:
                existing_delegate_ids[existing_wt] = row_idx

        processed_stats = {}
        processed_ids = set()
        rows_data = {}
        for data in email_Aidata_lst:
            wt = data["委托单号"]
            # 无委托单号，则不处理该行数据
            if wt == "主题中无委托单号":
                continue
            # 如果委托单号已存在工作表中
            if wt in existing_delegate_ids:
                row_idx = existing_delegate_ids[wt]
                row_updated = False
                if wt not in processed_stats.keys():
                    processed_stats[wt] = []
                # 更新可修改的字段, 跳过第1列和委托单号列
                for col_idx in range(1, len(english_headers)):
                    # 跳过委托单号列（不更新关键标识列）
                    if col_idx == delegate_col_index:
                        continue
                    # 获取当前列对应的中文列名
                    col_name = chinese_headers[col_idx]
                    if col_name in self.field_update_flags_map.keys():
                        old_value = output_ws.cell(row=row_idx, column=col_idx+1).value
                        if self.field_update_flags_map[col_name].field_name == "是" or (self.field_update_flags_map[col_name].field_name == "否" and (not old_value)):
                            # 获取新值（如果不存在则尝试英文列名）
                            new_value = data.get(col_name) or data.get(english_headers[col_idx])
                            if new_value is not None:
                                output_ws.cell(row=row_idx, column=col_idx+1, value=new_value)
                                self._set_field_by_name(output_ws, col_name + "_updated_from", chinese_headers, english_headers, row_idx, data["UID"])
                                row_updated = True
                                if col_name not in processed_stats[wt]:
                                    processed_stats[wt].append(col_name)
                                Logger.debug(f"委托单号 {wt} 的列 {col_name} 已更新，旧值：{old_value}，新值：{new_value}")
                            else:
                                Logger.debug(f"委托单号 {wt} 的列 {col_name} 无法更新，新值：{new_value}")
                        else:
                            Logger.debug(f"委托单号 {wt} 的列 {col_name}，更新标识：{self.field_update_flags_map[col_name].field_name } 无法更新，旧值：{old_value}")
                if row_updated:
                    # 更新数据更新时间
                    self._set_field_by_name(output_ws, update_time_header, chinese_headers, english_headers, row_idx, data["最新识别时间"])
            elif wt in processed_ids:
                # 更新已保存行数据中可更新的字段值,如果为否则看已处理的列值是否为空，如果为空则也可以更新初值，如果新值为空则不更新
                for col_name in data:
                    if col_name in self.field_update_flags_map.keys():
                        processed_old_value = rows_data[wt].get(col_name)
                        if self.field_update_flags_map[col_name].field_name == "是" or (self.field_update_flags_map[col_name].field_name == "否" and (not processed_old_value)):
                            # 根据AI识别字段名获取新值
                            new_value = data.get(col_name)
                            if new_value:
                                rows_data[wt][col_name] = new_value
                                rows_data[wt][col_name + "_updated_from"] = data["UID"]
                                Logger.debug(f"委托单号 {wt} 的列 {col_name} 已更新，已缓存值：{processed_old_value}，新值：{new_value}")
                            else:
                                Logger.debug(f"委托单号 {wt} 的列 {col_name} 无法更新，已缓存值：{processed_old_value}，新值：{new_value}")
                        else:
                            Logger.debug(f"委托单号 {wt} 的列 {col_name}，更新标识：{self.field_update_flags_map[col_name].field_name } 无法更新，已缓存值：{processed_old_value}")
            else:
                origin_col_lst = list(data.keys()) # data键值和映射文件键值的交集
                for col_name in origin_col_lst:
                    if col_name in self.field_update_flags_map.keys():
                        new_value = data[col_name]
                        if new_value:
                            data[col_name + "_updated_from"] = data["UID"] #这里会取所有在field_update_fields_map中的字段，包括委托单号_updated_from
                data[update_time_header] = data["最新识别时间"]
                rows_data[wt] = data
                processed_ids.add(wt)
        Logger.info(f"🎉 更新数据完成，详情：{processed_stats}")
        return rows_data.values(), chinese_headers
    def _convert_data_format(self, data):
        """
        将字典中所有值为 'YYYY-MM-DD' 格式的字符串转换为 'YYYY/M/D' 格式，"None"或None转换为空字符串'
        参数:
        data (dict): 需要处理的字典
        返回:
        dict: 处理后的字典
        """
        # 更新时间字段格式
        for key, value in data.items():
            if value is None or value=="None":
                value = ""
                continue
                
            # 检查是否为提示词示例数据，如果是则置空
            if isinstance(value, str) and is_prompt_example_value(key, value):
                data[key] = ""
                continue
            if isinstance(value, str) and key.upper() == "HBL" and len(value) > 20:
                data[key] = value[:20]
                continue
            if isinstance(value, str):
                try:
                    dt = datetime.strptime(value, '%Y-%m-%d')
                    data[key] = dt.strftime('%Y/%#m/%#d')
                except ValueError:
                    data[key] = value
                    pass  # 不是有效日期格式，保持原值
        
        return data

    def initialize_for_single_processing(self):
            """为单邮件处理模式初始化必要的配置"""
            try:
                # 初始化字段更新配置
                self.field_update_flags_map = FileProcessor.parse_mapping_dict(
                    self.sheet_conf, ':', '|', ',', '='
                )
                
                # 确保目标文件存在
                if not os.path.exists(self.target_file):
                    FileProcessor.create_newfile_by_template_retryable(
                        template_file=self.template_file, 
                        target_file=self.target_file, 
                        max_retries=5, 
                        retry_interval=5, 
                        start_index=2
                    )
                
                Logger.info("✅ 单邮件处理模式初始化完成")
                return True
                
            except Exception as e:
                Logger.error(f"单邮件处理模式初始化失败: {str(e)}")
                return False
    
    def _process_eml_body_Attachment(self, msg, email_Aidata_one, format_hms_date):
        """
        处理邮件正文，去除历史邮件、转发邮件、签名
        表格数据处理成指定格式文本
        输出正确的正文格式
        :param msg:邮件对象
        :return:
        """
        payload = None
        attachment_name_lst = []
        # 获取附件下载路径
        # attachment_dir = os.path.join(self.email_attachment_path, format_hms_date)# , email_Aidata_one['委托单号']
        # FileProcessor.ensure_directories_exist([self.email_attachment_path])

        # 多部分邮件（例如带有附件）处理邮件附件、正文、表格
        if msg.is_multipart():
            # 遍历所有板块
            for part in msg.walk():
                # 获取邮件的MIME类型
                mime_type = part.get_content_type()

                # 如果这是一个多部分邮件（例如带有附件），part.is_multipart()会返回True
                if part.is_multipart():
                    continue
                # 如果是文本内容，打印文本
                elif mime_type == 'text/plain':
                    payload = self._safe_decode(part.get_payload(decode=True))
                elif mime_type == 'text/html':
                    payload = self._safe_decode(part.get_payload(decode=True))
                    # 解析html，将表格数据替换成指定格式的文本
                    soup = BeautifulSoup(payload, 'html.parser')
                    tables = soup.find_all('table')  # 将表格数据获取出来

                    # 以下是表格数据的处理
                    table_str_lst = []
                    for table in tables:
                        temp_table = []
                        rows = table.find_all('tr')
                        for row in rows:
                            temp_row = []
                            cells = row.find_all(['td', 'th'])
                            for cell in cells:
                                temp_row.append(cell.text.strip())
                            temp_row_str = " | ".join(temp_row)
                            temp_table.append(temp_row_str)
                        temp_table_str = " ; ".join(temp_table)
                        temp_table_str = f"\n（以下数据是一个表格，';'是行的分隔符，'|'是列的分隔符,出现符号'###'说明此段表格数据结束）{temp_table_str}###\n"
                        table_str_lst.append(temp_table_str)
                        table.string = temp_table_str

                    # 分析正确，得到正文的正确格式
                    converter = html2text.HTML2Text()
                    converter.ignore_links = False  # 保留链接在将 HTML 转换为纯文本的过程中，不要忽略超链接（anchor tags <a>）
                    payload = converter.handle(str(soup))

                # 如果是附件，保存附件到文件
                elif mime_type.startswith('image'):
                    continue

                elif mime_type.startswith('application'):
                    # 获取附件文件名（处理编码）
                    original_filename = part.get_filename()
                    if original_filename:
                        filename, encoding = decode_header(original_filename)[0]
                        if isinstance(filename, bytes):
                            # 优先使用decode_header返回的编码信息
                            filename_decode = self._safe_decode(filename, encoding)
                            filename = filename_decode
                        # 清理非法字符
                        invalid_chars = r'[<>:"/\\|?*\r\n]'  # 包含换行符、回车符和其他非法字符
                        filename = re.sub(invalid_chars, '_', filename)  # 替换非法字符为下划线
                        filename = filename.strip()  # 去除首尾空白
                        filename = filename.rstrip('.')  # 去掉结尾的点，防止隐藏文件
                        if any(keyword in filename for keyword in self.email_attachment_keywords):
                            # 使用线程安全的附件保存方法
                             if FileProcessor.save_file_retryable(os.path.join(self.email_attachment_path, filename), part.get_payload(decode=True)):
                                attachment_name_lst.append(filename)
        else:
            payload = self._safe_decode(msg.get_payload(decode=True))

        # 判断数据是否正常，不正常反回空
        if not payload:
            email_Aidata_one["邮件正文"] = ""
            email_Aidata_one["邮件附件"] = ";".join(attachment_name_lst)
            email_Aidata_one["邮件附件数量"] = str(len(attachment_name_lst))
            return email_Aidata_one

        # 处理正文，去除历史邮件、转发邮件、签名
        # 组合正则表达式，忽略大小写和多行匹配
        pattern = re.compile('|'.join(self.forward_patterns), flags=re.IGNORECASE | re.MULTILINE)

        # 查找第一个匹配的位置
        match = pattern.search(payload)
        if match:
            cleaned_body = payload[:match.start()].strip()
        else:
            cleaned_body = payload.strip()

        # 最后检查未剔除的，重新剔除
        cleaned_body = self._find_substring_and_trim(cleaned_body, "发件人")
        cleaned_body = self._find_substring_and_trim(cleaned_body, "From:")
        cleaned_body = self._find_substring_and_trim(cleaned_body, "* * *")
        cleaned_body: object = cleaned_body.replace("\u200b", "").replace("\u0e07", "")

        email_Aidata_one["邮件正文"] = cleaned_body
        email_Aidata_one["邮件附件"] = ";".join(attachment_name_lst)
        email_Aidata_one["邮件附件数量"] = str(len(attachment_name_lst))
        return email_Aidata_one
    # 获取邮件内容
    def _parse_email(self, date_obj, email_Aidata_one):
        """解析邮件主题、时间、正文、附件，保存原始邮件、附件"""
        
        format_ymd_date = date_obj.strftime('%Y/%m/%d')
        now_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        
        # 使用EmailAIService的统一入口方法进行AI分析
        try:
            # 使用通义千问模型进行分析（可以根据需要改为use_deepseek=True）
            result = self.email_ai_service.analyze_email_content(
                email_sent_date=format_ymd_date,
                email_wt=email_Aidata_one['委托单号'],
                email_subject=email_Aidata_one['邮件主题'],
                email_body=email_Aidata_one['邮件正文'],
                use_deepseek=False  # 使用通义千问，如需DeepSeek可改为True
            )
            
            # 直接更新所有字段
            email_Aidata_one.update(result)
            
        except Exception as e:
            Logger.error(f"AI分析失败: {str(e)}")
            # 设置默认空值
            email_Aidata_one.update({
                '船名': '',
                '航次': '',
                'ETD': '',
                'ETA': '',
                '提货日期': '',
                'MBL': '',
                'HBL': '',
                'ATD': '',
                '箱号': '',
                '封号': '',
                '进仓时间': '',
                '出库时间': '',
                '进港时间': '',
                'ATA': '',
                '目的港清关完成时间': '',
                '送货时间': '',
                '船公司': '',
            })
            email_Aidata_one["最新识别结果"] = "AI分析失败"
            email_Aidata_one["最新识别时间"] = now_time
        
        return self._convert_data_format(email_Aidata_one)
        
    def process_single_email_with_uid(self, email_uid, msg_data):
        try:
            # 获取并备份邮件原始字符串数据
            raw_email = msg_data[0][1]
            original_msg = email.message_from_bytes(raw_email)

            date_obj = email.utils.parsedate_to_datetime(original_msg['Date'])
            format_hms_date = date_obj.strftime('%Y%m%d%H%M%S')

            # 构建识别结果对象
            email_Aidata_one = {
                "UID": email_uid.decode('utf-8') if isinstance(email_uid, bytes) else str(email_uid),
                "委托单号": None,
                "船名": None,
                "航次": None,
                "ETD": None,
                "ETA": None,
                "提货日期": None,
                "船公司": None,
                "MBL": None,
                "HBL": None,
                "revised ETD": None,
                "revised ETA": None,
                "ATD": None,
                "箱号": None,
                "封号": None,
                "箱型": None,
                "件数": None,
                "包装单位": None,
                "毛重": None,
                "体积": None,
                "货值": None,
                "币种": None,
                "INVOICE NO": None,
                "DN NO": None,
                "进仓时间": None,
                "出库时间": None,
                "进港时间": None,
                "ATA": None,
                "目的港清关完成时间": None,
                "送货时间": None,
                "事件类型": None,
                "事件状态": None,
                "事件描述": None,
                "邮件主题": None,
                "邮件发件人": None,
                "邮件发送时间": None,
                "邮件正文": None,
                "邮件附件": None,
                "邮件附件数量": None,
                "最新识别结果": None,
                "最新识别时间": None,
                "数据版本": None,
                "数据更新时间": None,
                "数据更新字段": None,
                "数据状态": None,
                "任务ID": None
            }
            # “邮件主题”
            subject, encoding = decode_header(original_msg['Subject'])[0] # 邮件头中的编码声明不正确，混合编码
            if isinstance(subject, bytes):
                subject_decode = self._safe_decode(subject)
                email_Aidata_one['邮件主题'] = subject_decode # subject.decode(detected_encoding or 'utf-8', errors='replace')
            else:
                email_Aidata_one['邮件主题'] = subject

            # "委托单号"
            email_Aidata_one['委托单号'] = self._get_ID(email_Aidata_one['邮件主题'])

            # "邮件发送时间"
            email_Aidata_one['邮件发送时间'] = date_obj.strftime('%Y-%m-%d %H:%M:%S')

            # "邮件发件人"
            sender, encoding = decode_header(original_msg['From'])[0] # 邮件头中的编码声明不正确，混合编码
            if isinstance(sender, bytes):
                sender_decode = self._safe_decode(sender)
                email_Aidata_one['邮件发件人'] = sender_decode # subject.decode(detected_encoding or 'utf-8', errors='replace')
            else:
                email_Aidata_one['邮件发件人'] = sender
            
            # 根据委托单号选则将邮件保存到本地
            if email_Aidata_one['委托单号'] == "主题中无委托单号":
                email_Aidata_one["最新识别结果"] = "无委托单号，无需识别"
            else:
                # 进一步获取"邮件正文"，"邮件附件"，"邮件附件数量"
                # email_Aidata_one = self._process_eml_body_Attachment(original_msg, email_Aidata_one, format_hms_date)
                # 分析邮件，得到结果数据
                email_Aidata_one = self._parse_email(date_obj, email_Aidata_one)      
            # 因为要处理报文邮件,所以无论主题有没有委托单号都要处理附件
            email_Aidata_one = self._process_eml_body_Attachment(original_msg, email_Aidata_one, format_hms_date)
            # 统计成功处理的邮件
            return email_Aidata_one
        
        except TimeoutError:
            Logger.error(f"❌ 处理邮件 {email_uid} 超时，跳过")
            return None
        except Exception as e:
            Logger.error(f"❌ 处理邮件时出错: {str(e)}")
            Logger.debug(f"{traceback.format_exc()}")
            return None
    
    # def process_single_email_with_uid(self, email_uid, msg_data):
    #     """处理指定UID的邮件 - 线程安全版本"""
    #     try:
    #         # base_delay = 1
    #         # import random, time
    #         # delay = base_delay + random.uniform(2, 5)
    #         # time.sleep(delay)
    #         # 使用线程本地的emailClient连接
    #         # local_client = EmailClient(email_config["imap_server"], email_config["imap_port"], email_config["imap_username"], email_config["imap_password"])
    #         # local_client.noop()
    #         # 直接处理指定的邮件
    #         email_Aidata_one = self._prosess_email_sync(email_uid, msg_data)
                    
    #         return email_Aidata_one
            
    #     except Exception as e:
    #         Logger.error(f"处理邮件 {email_uid} 时出错: {str(e)}")
    #         return None
        # finally:
        #     # 确保关闭连接
        #     if 'local_client' in locals():
        #         local_client._reset_connection()